if와 if else, for과 while을 언제 쓰는가

과제

const a = [1, 'ab"c', true, undefined, null, _ => 3];
JSON.stringify(a);

아래 법칙들을 이용해서 stringify 만들기 (꼬리재귀, 루프 두가지 방법으로)

  1. 변수의 스코프와 라이프사이클. 연산과 메모리의 관계
  2. 오류와 실패의 관계 - 오류는 중간요소의 내결합성 때문에 실패로 이어지지 않을 수 있다. 오류가 최대한 빨리 실패로 이어지게 짜라. 컨텍스트에러가 더 무서운 것 -> 신뢰성, 안정성(컨텍스트에러발생 올라감)은 trade off (js는 컴파일타임에 잡을 수 없으니까 런타임에 에러 뿜도록)
  3. 코드의 분리 - 수정되는 원인에 따라, 변화율(변화율 같은 애들끼리 모으기)
  4. js 인터페이스란 함수의 이름, 인자, 반환값 형식이 일치하는 경우
  5. 인터페이스 일치시키면 컬렉션으로 묶을 수 있다 -> 일종의 일반화 -> 서로 다른 형태를 인터페이스를 일치시켜 일반화한다.
  6. 데이터와 데이터를 이용한 알고리즘이 이원화되면 관리가 불가능 -> 데이터를 소유한 쪽에서 데이터를 사용하는 알고리즘을 제공한다.
  7. 꼬리최적화 함수를 루프로 고칠때는 기계적으로 수정
  8. 루프는 클로저에만 의존하는 함수를 반복시키고, 재귀함수는 인자에만 의존하는 함수를 반복시킨다.
  9. 반복되는 코드를 제거하기 위해 집착하자.

모범풀이

재귀 풀이

const stringCheck = [
  [/[\r\n\l]/g, '\\n'],
  [/"/g, '\\"'],
  [/\t/g, '\\t'],
];

const el = {
  number: v => v.toString(),
  boolean: v => v.toString(),
  string: v =>
    `"${stringCheck.reduce((acc, cur) => acc.replace(cur[0], cur[1]), v)}"`,
  stringify(v) {
    return this[typeof v]?.(v) ?? 'null';
  },
};

const recursive = (arr, acc, i) => {
  return i < arr.length
    ? recursive(arr, acc + `,${el.stringify(arr[i])}`, i + 1)
    : `[${acc.substr(1)}]`;
};

const stringify = arr => {
  if (!Array.isArray(arr)) throw 'invalid array';
  return arr.length === 0 ? '[]' : recursive(arr, '', 0);
};

stringify([1, 'a', () => {}]);

루프문 풀이

const stringCheck = [
  [/[\r\n\l]/g, '\\n'],
  [/"/g, '\\"'],
  [/\t/g, '\\t'],
];

const el = {
  number: v => v.toString(),
  boolean: v => v.toString(),
  string: v =>
    `"${stringCheck.reduce((acc, cur) => acc.replace(cur[0], cur[1]), v)}"`,
  stringify(v) {
    return this[typeof v]?.(v) ?? 'null';
  },
};

const arrValidator = arr => {
  if (!Array.isArray(arr)) throw 'invalid array';
};
let EMPTY = {};

const stringify = arr => {
  // validator 코드분리
  arrValidator(arr);
  let result = EMPTY;

  // if는 optional, shield 일 경우 쓴다.
  // if else 는 binary mandatory일 경우 쓴다.
  if (arr.length === 0) result = '[]';
  else {
    let acc = '',
      i = 0;
    while (i < acc.length) {
      acc + `,${el.stringify(arr[i])}`;
    }
    result = `[${acc.substr(1)}]`;
  }
  // 지금은 코드가 단순하지만 복잡해지면 아래와 같은 가드를 둬야 한다.
  // if else는 무조건 둘중하나 거치기 때문에 result가 변하는게 맞다.
  if (result === EMPTY) throw 'no processed';
  return result;
};
  • if : optional, shield
  • if else : binary mandatory
  • switch : multiply mandatory
  • while : recursive 사전에 계획되지 않은 반복, 반복시마다 다음 반복을 계산한다
  • for : 사전에 계획된 반복, 이미 사전에 반복을 어떻게 할지 계획을 세웠다.

재귀를 루프문으로 번역할 때는 while을 쓰는게 옳다.

강의링크

코드스피츠 Programming101 - 3강


Written by@Jiyon Lee
뜨거운 코드를 가르며

GitHub